using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using System.Xml.Serialization;
using System.Text.RegularExpressions;

namespace WOWEquipOptimizer
{
    [XmlInclude(typeof(Armor)), XmlInclude(typeof(Gem)), XmlInclude(typeof(WeaponSkill)), XmlInclude(typeof(Ring)), XmlInclude(typeof(Trinket)), XmlInclude(typeof(Weapon))]
    public abstract partial class Item
    {
        public static Attribute parseAttribute(int value, string type)
        {
            try
            {
                if (type == "DefenseSkillRating") type = "DefenseRating";
                AttributeName name = (AttributeName)Enum.Parse(typeof(AttributeName), type, true);
                return new Attribute(name, value);
            }
            catch
            {
                Console.WriteLine("Something went wrong");
            }
            return new Attribute(AttributeName.None, 0);
        }
        public static AttributeName GenericPartialAttrMatcher(string bonus)
        {
            string[] attr = Enum.GetNames(typeof(AttributeName));
            for (int i = 1; i < attr.Length; i++)
            {
                List<string> attrsplit = new List<string>();
                Regex attrregex = new Regex(@"(?<value>[A-Z]{1}[a-z]+)");
                int start = 0;
                while (true)
                {
                    Match myAttrMatch = attrregex.Match(attr[i], start);
                    if (myAttrMatch.Success)
                    {
                        attrsplit.Add(myAttrMatch.Result("${value}").ToLower());
                        start = myAttrMatch.Index + myAttrMatch.Length;
                    }
                    else break;
                }
                if (attrsplit.Count == 0) continue;
                // remove strike\s
                bonus = bonus.Replace("strike ", "").ToLower();
                string[] bsplit = bonus.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                if (attrsplit.Count != bsplit.Length) continue;
                int counter = 0;
                for (int j = 0; j < bsplit.Length; j++)
                {
                    for (int k = 0; k < attrsplit.Count; k++)
                    {
                        if (bsplit[j].Contains(attrsplit[k]))
                        {
                            counter++;
                        }
                    }
                }
                if (counter == attrsplit.Count)
                {
                    return (AttributeName)i;
                }
            }
            return SpecialMatcher(bonus);
        }
        public static AttributeName SpecialMatcher(string bonus)
        {
            switch (bonus)
            {
                case "damage and healing done by magical spells and effects":
                    return AttributeName.SpellDamage;
                case "damage done by nature spells and effects":
                    return AttributeName.NatureSpellDamage;
                case "damage done by fire spells and effects":
                    return AttributeName.FireSpellDamage;
                case "damage done by frost spells and effects":
                    return AttributeName.FrostSpellDamage;
                case "damage done by shadow spells and effects":
                    return AttributeName.ShadowSpellDamage;
                case "damage done by arcane spells and effects":
                    return AttributeName.ArcaneSpellDamage;
                case "damage done by holy spells and effects":
                    return AttributeName.HolySpellDamage;
                case "healing spells":
                case "healing done by spells and effects":
                    return AttributeName.Healing;
                case "mana every 5 seconds":
                case "mana every 5 sec":
                case "mana per 5 seconds":
                case "mana per 5 sec":
                    return AttributeName.MP5;
                case "health per 5 sec":
                    return AttributeName.HP5;
                case "the block value of your shield":
                    return AttributeName.BlockValue;
                case "resilience":
                    return AttributeName.ResilienceRating;
                case "all resistances":
                    return AttributeName.ResistAll;
                default:
                    return AttributeName.None;
            }
        }
        public static List<Attribute> parseDescription(string desc)
        {
            List<Attribute> attrs = new List<Attribute>();

            Regex incregex = new Regex(@"^Increases\s(your\s|)(?<bonus>[\s\w]+)\sby\s(up\sto\s|)(?<value>\d+)\s?(?<condition>[a-zA-Z\s\,]*)\.$", RegexOptions.IgnoreCase);
            Regex valfirst = new Regex(@"((?<condition>[a-zA-Z\s\,]*)\s|)\+?(?<value>\d+)\s(?<bonus>[\s\w]+)\.?$", RegexOptions.IgnoreCase);
            Regex vallast = new Regex(@"(?<bonus>[\s\w]+)\s\+?(?<value>\d+)\s?(?<condition>[a-zA-Z\s\,]*)\.?$", RegexOptions.IgnoreCase);
            Match myMatch = incregex.Match(desc);
            if (!myMatch.Success)
            {
                Match valfirstMatch = valfirst.Match(desc);
                if (!valfirstMatch.Success)
                {
                    Match vallastMatch = vallast.Match(desc);
                    if (!vallastMatch.Success)
                    {

                    }
                    else
                    {
                        myMatch = vallastMatch;
                    }
                }
                else
                {
                    myMatch = valfirstMatch;
                }
            }
            if(myMatch.Success)
            {
                string bonus = myMatch.Result("${bonus}");
                try
                {
                    int value = Convert.ToInt32(myMatch.Result("${value}"));
                    string condition = myMatch.Result("${condition}");
                    AttributeName attrname = GenericPartialAttrMatcher(bonus);
                    if (attrname != AttributeName.None)
                    {
                        if (condition.Length == 0)
                        {
                            attrs.Add(new Attribute(attrname, value));
                        }
                        else
                        {
                            switch (condition)
                            {
                                case "in Cat, Bear, Dire Bear, and Moonkin forms only":
                                    {
                                        attrs.Add(new Attribute(AttributeName.FeralAP, value));
                                        break;
                                    }
                                case "Restores":
                                    {
                                        attrs.Add(new Attribute(attrname, value));
                                        break;
                                    }
                                default:
                                    Console.WriteLine("Condition not found: " + desc);
                                    break;
                            }
                        }
                    }
                    else if (!bonus.Contains("skill rating")) // weapon skill ratings are handled elsewhere
                    {
                        switch (bonus.ToLower())
                        {
                            case "defense":
                                attrs.Add(new Attribute(AttributeName.DefenseRating, value * Character.DefenseSkillRating));
                                break;
                            default:
                                Console.WriteLine("Attr.None Exception: " + desc);
                                break;

                        }
                    }

                }
                catch (Exception)
                {
                     Console.WriteLine("Match exception: "+desc);
                }
            }
            else
            {
                 Console.WriteLine("Not matched: " + desc);
            }
            return attrs;
        }
        // sockets still todo
        public static WeaponSkill parseWeaponSkill(string desc)
        {
            WeaponSkill retval = new WeaponSkill();
            if (desc.Contains("two-handed "))
            {
                return retval; // no support for two handed atm
            }
            Regex incregex = new Regex(@"^Increases\s(your\s|)(?<bonus>[\s\w]+)\sby\s(?<value>\d+)\.$", RegexOptions.IgnoreCase);
            Match myMatch = incregex.Match(desc);
            if (myMatch.Success)
            {
                string bonus = myMatch.Result("${bonus}");
                bonus = bonus.Replace("skill rating", "");
                bonus = bonus.Replace("feral combat", "feral");
                bonus = bonus.Replace("fist ", "fistweapon");
                try
                {
                    retval.Weapon = (WeaponType)Enum.Parse(typeof(WeaponType), bonus, true);
                    retval.Value = Convert.ToInt32(myMatch.Result("${value}"));

                }
                catch (Exception)
                {
                }
            }
            return retval;
        }
        public static List<Item> generateEquipList(ref DataSet input)
        {
            List<Item> retval = new List<Item>();
            string[] bonusarray =
            {
                "bonusCritRating","bonusHitRating","bonusHasteRating",
                "bonusCritMeleeRating","bonusHitMeleeRating","bonusDodgeRating","bonusDefenseSkillRating",
                "bonusResilienceRating","bonusParryRating","bonusBlockRating",
                "bonusHitSpellRating","bonusHasteSpellRating","bonusCritSpellRating","bonusStrength",
                "bonusStamina","bonusAgility","bonusIntellect","bonusSpirit", 
                "ShadowResist", "NatureResist", "FrostResist", "FireResist", "ArcaneResist",
            };
            DataTable mainTable = input.Tables["ItemTooltip"];
            for (int i = 0; i < mainTable.Rows.Count; i++)
            {
                DataRow row = mainTable.Rows[i];
                int id = (ushort)row["id"];
                if (id == 32262)
                {
                    Console.WriteLine("Found it");
                }
                //else continue;
                string name = (string)row["name"];
                string icon = (string)row["icon"];
                string subtype = string.Empty;
                string desc = string.Empty;
                int quality = (Byte)row["overallQualityId"];
                int classid = (Byte)row["classId"];
                float dps = 0f;
                float speed = 0f;
                bool unique = false;
                List<Attribute> attrs = new List<Attribute>();
                int invtype = 0;
                int armor = 0;
                bool isgem = false;
                string gemprop = string.Empty;
                string socketmatchdesc = string.Empty;
                int socketcount = 0;
                bool hasset = false;
                CharacterClass allowable = CharacterClass.None;
                ItemSource itemsource = ItemSource.None;
                string setname = string.Empty;
                List<string> setitemnames = new List<string>();
                List<string> setboni = new List<string>();
                List<int> settreshold = new List<int>();
                List<string> spelldesc = new List<string>();
                List<int> spelltrigger = new List<int>();
                List<GemType> gems = new List<GemType>();
                List<WeaponSkill> wskills = new List<WeaponSkill>();
                int blockvalue = 0;
                if (row["armor"].GetType() != typeof(System.DBNull))
                {
                    armor = Convert.ToInt32(row["armor"]);
                }
                if (row["blockValue"].GetType() != typeof(System.DBNull))
                {
                    blockvalue = Convert.ToInt32(row["blockValue"]);
                }
                if (row["desc"].GetType() != typeof(System.DBNull))
                {
                    desc = (string)row["desc"];
                }
                DataRow[] equip = (row.GetChildRows("ItemTooltip_equipData"));
                if (equip.Length == 0) continue;
                invtype = Convert.ToInt32(equip[0]["inventoryType"]);
                if (equip[0]["subclassName"].GetType() != typeof(System.DBNull))
                {
                    subtype = (string)equip[0]["subclassName"];
                }
                DataRow[] maxcountrow = (row.GetChildRows("ItemTooltip_maxCount"));
                if (maxcountrow.Length > 0)
                {
                    int val1 = Convert.ToInt32(maxcountrow[0][1]);
                    if (val1 == 1) unique = true;
                }
                for (int k = 0; k < bonusarray.Length; k++)
                {
                    string col = bonusarray[k];
                    int value = 0;
                    if (row[col].GetType() != typeof(System.DBNull))
                    {
                        value = Convert.ToInt32(row[col]);
                        Attribute attr = parseAttribute(value, bonusarray[k].Replace("bonus",""));
                        if (attr.Type != AttributeName.None)
                        {
                            attrs.Add(attr);
                        }
                    }
                }
                if (blockvalue > 0) attrs.Add(new Attribute(AttributeName.BlockValue, blockvalue));
                // allowableclass
                DataRow[] allow = row.GetChildRows("ItemTooltip_allowableClasses");
                if (allow.Length > 0)
                {
                    DataRow[] classes = allow[0].GetChildRows("allowableClasses_class");
                    foreach (DataRow cls in classes)
                    {
                        allowable |= (CharacterClass)Enum.Parse(typeof(CharacterClass), (string)cls[0], true);
                    }
                }
                //itemSource
                DataRow[] itemsourcerow = row.GetChildRows("ItemTooltip_itemSource");
                if (itemsourcerow.Length > 0)
                {
                    //sourceType.none
                    if (itemsourcerow[0][0].GetType() != typeof(System.DBNull))
                    {
                        string source = (((string)itemsourcerow[0][0]).Split('.'))[1];
                        //public enum ItemSource { None = 0, Drop, Quest, Recipie, Vendor, PVP, Faction, Other };
                        switch (source)
                        {
                            case "createdByPlans":
                                {
                                    itemsource = ItemSource.Recipie;
                                    break;
                                }
                            case "providedQuest":
                            case "questReward":
                                {
                                    itemsource = ItemSource.Quest;
                                    break;
                                }
                            case "vendor":
                                {
                                    itemsource = ItemSource.Vendor;
                                    break;
                                }
                            case "pvpReward":
                                {
                                    itemsource = ItemSource.PVP;
                                    break;
                                }
                            case "factionReward":
                                {
                                    itemsource = ItemSource.Faction;
                                    break;
                                }
                            case "creatureDrop":
                                {
                                    itemsource = ItemSource.Drop;
                                    break;
                                }
                            case "worldDrop":
                            case "gameObjectDrop":
                                {
                                    itemsource = ItemSource.Drop;
                                    break;
                                }
                            default:
                                itemsource = ItemSource.Other;
                                break;
                        }
                    }
                }
                // gem
                if (row["gemProperties"].GetType() != typeof(System.DBNull))
                {
                    isgem = true;
                    gemprop = (string)row["gemProperties"];
                }
                // damage
                DataRow[] damage = row.GetChildRows("ItemTooltip_damageData");
                if (damage.Length > 0)
                {
                    if (damage[0][0].GetType() != typeof(System.DBNull) && damage[0][1].GetType() != typeof(System.DBNull))
                    {
                        dps = (float)Convert.ToDouble(damage[0][1]);
                        speed = (float)Convert.ToDouble(damage[0][0]);
                    }
                }
                // socket
                DataRow[] socket = (row.GetChildRows("ItemTooltip_socketData"));
                if (socket.Length > 0)
                {
                    socketmatchdesc = (string)socket[0][0];
                    DataRow[] sockets = socket[0].GetChildRows("socketData_socket");
                    foreach (DataRow sock in sockets)
                    {
                        string color = (string)sock[0];
                        gems.Add((GemType)Enum.Parse(typeof(GemType), color, true));
                        socketcount++;
                    }
                }
                // set
                DataRow[] set = (row.GetChildRows("ItemTooltip_setData"));
                if (set.Length > 0)
                {
                    setname = (string)set[0][0];
                    DataRow[] setitems = set[0].GetChildRows("setData_item");
                    foreach (DataRow item in setitems)
                    {
                        string itemname = (string)item[0];
                        setitemnames.Add(itemname);
                    }
                    DataRow[] setbonus = set[0].GetChildRows("setData_setBonus");
                    foreach (DataRow item in setbonus)
                    {
                        string bonusname = (string)item[0];
                        setboni.Add(bonusname);
                        int tresh = Convert.ToInt32(item[1]);
                        settreshold.Add(tresh);
                    }
                    hasset = true;
                }
                // spell
                DataRow[] spelldata = (row.GetChildRows("ItemTooltip_spellData"));
                if (spelldata.Length > 0)
                {
                    DataRow[] ispell = spelldata[0].GetChildRows("spellData_spell");
                    foreach (DataRow spell in ispell)
                    {
                        string spelld = (string)spell[1];
                        spelldesc.Add(spelld);
                        int trigger = Convert.ToInt32(spell[0]);
                        spelltrigger.Add(trigger);
                    }
                }
                if (isgem)
                {
                    Gem mygem = new Gem();
                    mygem.Id = id;
                    mygem.Name = name;
                    mygem.Icon = icon;
                    mygem.Quality = (Quality)quality;
                    mygem.Type = Gem.parseGemType(desc);
                    string[] splitted = gemprop.Split(new string[] { " and ", ", ", " & ", }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (string split in splitted)
                    {
                        List<Attribute> list = Item.parseDescription(split);
                        if (list != null && list.Count > 0) mygem.Attributes.AddRange(list);
                        else
                        {
                            mygem.Attributes.Add(new Attribute(AttributeName.Special, (uint)split.GetHashCode() % 38063411));
                        }

                    }
                    if (allowable > 0) mygem.AllowableClasses = allowable;
                    mygem.Unique = unique;
                    retval.Add(mygem);
                    continue;
                }
                Item parsed = null;
                switch (invtype)
                {
                    case 0:
                        //gems are handled above
                        continue;
                    case 1:
                        parsed = new Armor();
                        ((Armor)parsed).Slot = ArmorSlot.Head;
                        break;
                    case 2:
                        parsed = new Armor();
                        ((Armor)parsed).Slot = ArmorSlot.Neck;
                        break;
                    case 3:
                        parsed = new Armor();
                        ((Armor)parsed).Slot = ArmorSlot.Shoulder;
                        break;
                    case 20:
                    case 5:
                        parsed = new Armor();
                        ((Armor)parsed).Slot = ArmorSlot.Chest;
                        break;
                    case 6:
                        parsed = new Armor();
                        ((Armor)parsed).Slot = ArmorSlot.Waist;
                        break;
                    case 7:
                        parsed = new Armor();
                        ((Armor)parsed).Slot = ArmorSlot.Legs;
                        break;
                    case 8:
                        parsed = new Armor();
                        ((Armor)parsed).Slot = ArmorSlot.Feet;
                        break;
                    case 9:
                        parsed = new Armor();
                        ((Armor)parsed).Slot = ArmorSlot.Wrist;
                        break;
                    case 10:
                        parsed = new Armor();
                        ((Armor)parsed).Slot = ArmorSlot.Hands;
                        break;
                    case 11:
                        parsed = new Ring();
                        break;
                    case 12:
                        parsed = new Trinket();
                        break;
                    case 13:
                        parsed = new Weapon();
                        ((Weapon)parsed).WeaponSlot = WeaponSlot.MainHand | WeaponSlot.OffHand;
                        break;
                    case 14:
                        parsed = new Armor();
                        ((Armor)parsed).Slot = ArmorSlot.Shield;
                        break;
                    case 15:
                    case 25:
                    case 26:
                    case 28:
                        parsed = new Weapon();
                        ((Weapon)parsed).WeaponSlot = WeaponSlot.Ranged;
                        break;
                    case 16:
                        parsed = new Armor();
                        ((Armor)parsed).Slot = ArmorSlot.Back;
                        break;
                    case 17:
                        parsed = new Weapon();
                        ((Weapon)parsed).WeaponSlot = WeaponSlot.MainHand;
                        ((Weapon)parsed).TwoHand = true;
                        break;
                    case 21:
                        parsed = new Weapon();
                        ((Weapon)parsed).WeaponSlot = WeaponSlot.MainHand;
                        break;
                    case 22:
                        parsed = new Weapon();
                        ((Weapon)parsed).WeaponSlot = WeaponSlot.OffHand;
                        break;
                    case 23:
                        parsed = new Armor();
                        ((Armor)parsed).Slot = ArmorSlot.OffHand;
                        break;
                    default:
                        continue;
                }
                parsed.Id = id;
                parsed.Name = name;
                parsed.Icon = icon;
                parsed.Quality = (Quality)quality;
                parsed.SocketCount = socketcount;
                parsed.Armor = armor;
                if (allowable > 0) parsed.AllowableClasses = allowable;
                parsed.Unique = unique;
                parsed.Attributes.AddRange(attrs);
                parsed.Source = itemsource;
                parsed.WeaponSkills = wskills;
                parsed.GemColors = new GemType[3];
                parsed.Gems = new Gem[socketcount];
                parsed.GemBonus = new List<Attribute>();
                if (parsed.GetType() == typeof(Weapon))
                {
                    ((Weapon)parsed).DPS = dps;
                    ((Weapon)parsed).Speed = speed;
                    string weapontype = subtype;
                    if (subtype.Length == 0) continue;
                    while (weapontype.IndexOf(' ') != -1)
                    {
                        weapontype = weapontype.Remove(weapontype.IndexOf(' '), 1);
                    }
                    ((Weapon)parsed).Type = (WeaponType)Enum.Parse(typeof(WeaponType), weapontype, true);
                }
                if (parsed.GetType() == typeof(Armor) && subtype.Length > 0)
                {
                    ((Armor)parsed).Type = (ArmorProficiency)Enum.Parse(typeof(ArmorProficiency), subtype, true);
                }
                if (socketcount > 0)
                {
                    parsed.GemColors = gems.ToArray();
                    List<Attribute> list = Item.parseDescription(socketmatchdesc);
                    if (list != null && list.Count > 0) parsed.GemBonus.AddRange(list);
                    else
                    {
                        parsed.GemBonus.Add(new Attribute(AttributeName.Special, (uint)socketmatchdesc.GetHashCode() % 38063411));
                    }

                }
                if (hasset)
                {
                    Set mySet = new Set(setname);
                    mySet.Item = setitemnames;
                    mySet.Treshold = settreshold;
                    for(int z = 0;z<setboni.Count;z++)
                    {
                        // notice: Some setboni to abilities are implicitly converted to passive setboni, since usually these boni are actually useful, this is fine for now
                        // the correct way would be some callback check tbh
                        List<Attribute> list = Item.parseDescription(setboni[z]);
                        if (list != null && list.Count > 0)
                        {
                            mySet.Bonus.AddRange(list);
                        }
                        else
                        {
                            mySet.Bonus.Add(new Attribute(AttributeName.Special, (uint)setboni[z].GetHashCode() % 38063411));
                            //Console.WriteLine("Bad Bonus: " + parsed.Name +", "+ bonus); look fine
                        }
                    }
                    parsed.Set = mySet;
                }
                for (int z = 0; z < spelldesc.Count; z++)
                {
                    List<Attribute> list = Item.parseDescription(spelldesc[z]);
                    if (spelldesc[z].Contains(" skill rating by "))
                    {
                        wskills.Add(parseWeaponSkill(spelldesc[z]));
                    }
                    if (list != null && list.Count > 0 && spelltrigger[z] == 1) parsed.Attributes.AddRange(list);
                    else
                    {
                        parsed.Attributes.Add(new Attribute(AttributeName.Special, (uint)spelldesc[z].GetHashCode() % 38063411));
                    }
                }
                retval.Add(parsed);
            }
            // fixing possible set m_Items
            foreach (Item item in retval)
            {
                if (item.Set.Name != null)
                {
                    foreach (Item check in retval)
                    {
                        if (item.Set.Item.Contains(check.Name))
                        {
                            if ((int)check.AllowableClasses < (int)item.AllowableClasses)
                            {
                                item.AllowableClasses = check.AllowableClasses;
                            }
                            else if ((int)check.AllowableClasses > (int)item.AllowableClasses)
                            {
                                check.AllowableClasses = item.AllowableClasses;
                            }
                        }
                    }
                }
            }
            return retval;
        }
    }
}